Google CalendarのUIを真似てCalendar UIを作る:本家風実装
週表示
時刻目盛りが3つくらい重なっている
理由不明
月表示
表:div[role="grid"] flex
曜日:div[role="raw"] flex
中身:div[role="presentation"] flex
週:div[role="row"] flex
週の数だけ並んでいる
週番号:div.ok6kbe
マス目リスト:div.sLvTye flex
日付リスト:div.umyEjb flex
マス目とわざわざ別のリストにして上から重ねている理由は不明
予定リスト:div.T3BIT div[role="presentation"] flex
各日付の予定リスト:div[role="gridcell"]
予定:div[role="presentation"] div
これだけ中身の位置と大きさが直接指定されている
終日の予定と時間指定ありの予定とでDOM構造の違いなし DOMをにらめっこしながら実装してみたもの
https://gyazo.com/ddf93fee5e44b1ce8e5d15ec0f3f909d
row headerは固定幅
column headerの幅と日付の幅とは一見あっているように見えるが、よく見るとややずれがある
無理やり合わせている印象takker.icon
code:app.tsx
/** @jsx h */
import { h, render} from "../preact/mod.tsx";
import { Calendar } from "./Calendar.tsx";
const div = document.createElement("div");
const shadowRoot = div.attachShadow({ mode: "open" });
document.body.append(div);
// 消すときはカレンダーをクリックする
render(<Calendar onClose={() => div.remove()}/>, shadowRoot);
code:Calendar.tsx
/** @jsx h */
/** @jsxFrag Fragment */
import { h, Fragment } from "../preact/mod.tsx";
import { getAllDaysInCalendar } from "../getAllDaysInCalendar/mod.ts";
import { isToday, getWeek, getDate, getMonth } from "../date-fns/mod.ts";
export const Calendar = ({ onClose }) => {
const weeks = getAllDaysInCalendar(new Date());
const month = getMonth(new Date());
return (<>
<style>{`
:host {
position: fixed;
top: 10%;
left: 10%;
margin: 0 auto;
min-width: 30vw;
min-height: 50vh;
}
.calendar-wrap {
position: absolute;
inset: 0;
outline: none;
overflow: hidden;
background-color: white;
display: flex;
flex-direction: column;
--row-header-width: 24px;
--column-header-height: 20px;
.headers {
margin: 0;
height: var(--column-header-height);
display: flex;
flex: none;
align-items: stretch;
.corner {
position: relative;
top: calc(var(--column-header-height) / 2);
height: calc(var(--column-header-height) / 2);
width: var(--row-header-width);
background-color: rgb(241,243,244);
border-top-left-radius: 4px;
border-top-right-radius: 4px
}
.column {
border-right: rgb(218,220,224) 1px solid;
flex: 1 1 0%;
text-align: center;
text-transform: uppercase;
font-size: 11px;
font-weight: 500;
line-height: 20px;
}
}
.body {
margin: 0;
overflow: hidden;
flex: 1 1 0%;
display: flex;
flex-direction: column;
.week {
position: relative;
overflow: hidden;
border-bottom: rgb(218,220,224) 1px solid;
display: flex;
flex: 1 1 0%;
.header {
width: var(--row-header-width);
padding-top: 8px;
text-align: center;
color: rgb(60, 64, 67);
font-size: 12px;
font-weight: 500;
letter-spacing: .3px;
line-height: 16px;
}
.row {
position: absolute;
inset: 0;
left: var(--row-header-width);
display: flex;
.cell {
font-size: 14px;
line-height: 30px;
text-align: center;
background: transparent;
flex: 1 1 0%;
border-right: rgb(218,220,224) 1px solid;
overflow: hidden; /* セルの中身で大きさが変化しなくなる */
h2 {
margin-top: 8px;
font-size: 12px;
font-weight: 500;
letter-spacing: .3px;
display: inline-block;
text-align: center;
white-space: nowrap;
width: max-content;
min-width: 24px;
line-height: 16px;
pointer-events: auto;
}
}
.cell:last-child {
border-right: none;
}
}
}
}
}
`}</style>
<div className="calendar-wrap" onClick={onClose} role="grid">
<div className="headers" role="row">
<div className="corner" />
(day) => (<div className="column" role="columnheader">{day}</div>)
)}
</div>
<div className="body" role="presentation">
{weeks.map((dates) => <Week dates={dates} />)}
</div>
</div>
</>);
};
const Week = ({ dates }) => {
return (<div className="week">
<div className="header" aria-hidden="true">{getWeek(dates0)}</div> <div className="row" aria-hidden="true">
{dates.map((date) => (<div className="cell">
<h2>{getDate(date) !== 1 ? getDate(date) : ${getMonth(date)}月${getDate(date)}日}</h2>
</div>))}
</div>
</div>);
};